package utils

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"golang.org/x/crypto/pbkdf2"
	"math/rand"
	"strings"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func RandSaltString(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

func internalPwdHash(password, salt string, iter int) string {
	resultBytes := pbkdf2.Key([]byte(password), []byte(salt), iter, 20, sha1.New)
	return hex.EncodeToString(resultBytes)
}

func PwdHash(password string, salt string) string {
	iter := 1000
	hashVal := internalPwdHash(password, salt, iter)
	prefix := "pbkdf2:sha1:1000"
	result := strings.Join([]string{prefix, salt, hashVal}, "$")
	return result
}

func PwdHashWithRandomSalt(password string) string {
	return PwdHash(password, RandSaltString(8))
}

func CheckPwdHash(pwdHash, password string) bool {
	if strings.Count(pwdHash, "$") < 2 {
		return false
	}
	hashSlice := strings.SplitN(pwdHash, "$", 3)
	salt := hashSlice[1]
	return PwdHash(password, salt) == pwdHash
}

var pwdValidLetterMap = make(map[int32]bool)
var pwdValidSmallLetterMap = make(map[int32]bool)
var pwdValidBigLetterMap = make(map[int32]bool)
var pwdValidNumberMap = make(map[int32]bool)
var pwdValidSymbolMap = make(map[int32]bool)
var pwdValidCharMap = make(map[int32]bool)

func init() {
	for _, char := range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" {
		pwdValidLetterMap[char] = true
		pwdValidCharMap[char] = true
	}

	for _,letter := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ"{
		pwdValidBigLetterMap[letter] = true
	}

	for _,letter := range "abcdefghijklmnopqrstuvwxyz"{
		pwdValidSmallLetterMap[letter] = true
	}

	for _, char := range "0123456789" {
		pwdValidNumberMap[char] = true
		pwdValidCharMap[char] = true
	}
	for _, char := range "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" {
		pwdValidSymbolMap[char] = true
		pwdValidCharMap[char] = true
	}
}

func contains(pwd string, validMap map[int32]bool) bool {
	for _, v := range pwd {
		if _, ok := validMap[v]; ok {
			return true
		}
	}
	return false
}

func notContainOthers(pwd string, validMap map[int32]bool) bool {
	for _, v := range pwd {
		if _, ok := validMap[v]; !ok {
			return false
		}
	}
	return true
}

func CheckStrPwdQualify(pwd string) error {
	if len(pwd) > 16 || len(pwd) < 6 {
		return errors.New("password length must between 6 and 16")
	}
	if contains(pwd, pwdValidLetterMap) && contains(pwd, pwdValidNumberMap) &&
		contains(pwd, pwdValidSymbolMap) && notContainOthers(pwd, pwdValidCharMap) {
		return nil
	} else {
		return errors.New("password must contains and only contains letter, num, and symbol")
	}
}

func ContainsSmallLetter(pwd string) bool{
	return contains(pwd,pwdValidSmallLetterMap)
}

func ContainsBigLetter(pwd string) bool{
	return contains(pwd,pwdValidBigLetterMap) && ContainsSmallLetter(pwd)
}

func ContainsLetter(pwd string) bool{
	return contains(pwd,pwdValidBigLetterMap) || contains(pwd,pwdValidSmallLetterMap)
}

func ContainsNumber(pwd string) bool {
	return contains(pwd,pwdValidNumberMap)
}

func ContainsSymbol(pwd string) bool  {
	return contains(pwd,pwdValidSymbolMap)
}
